﻿using System;
using System.Collections.Generic;
using System.Linq;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for deleting a project part.
    /// </summary>
    /// <remarks>
    /// Input Parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     DeletedPart (<see cref="IProjectExplorerItem"/>)
    /// </term>
    /// <description>
    ///     The project part that is to be deleted.
    /// </description>
    /// </item>
    /// </list>
    /// The command can execute if allits input parameters are set to non-null values.
    /// </remarks>
    public class DeleteProjectPartCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="DeleteProjectPartCommand"/> instance.
        /// </summary>
        /// <param name="undoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack where the command will be put after its execution.
        /// </para>
        /// </param>
        /// <param name="redoStack">
        /// <para>
        /// Type: <see cref="CommandStack"/>
        /// </para>
        /// <para>
        /// The command stack where the command will be put when it has been undone.
        /// </para>
        /// </param>
        public DeleteProjectPartCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            subCommands = new List<DeleteProjectPartCommand>();
            AddParameter<IProjectExplorerItem>("DeletedPart", ParameterDirection.In);

            Name = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Delete");
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            foreach (IProjectExplorerItem child in DeletedPart.Children)
                subCommands.Add(
                    new DeleteProjectPartCommand(UndoStack, RedoStack)
                    {
                        DeletedPart = child,
                        UseStacks = false
                    }
                    );

            foreach (var command in subCommands)
                command.Execute(parameter);

            if (DeletedPart is Directory)
            {
                DeletedPart.Parent.Children.Remove(DeletedPart);
                return;
            }
            else if (DeletedPart is Elements.Page)
            {
                var page = (Elements.Page)DeletedPart;
                var score = (Model.Elements.ScoreElement)page.FindOwner<Elements.Score>().AdaptedElement;

                ProjectExplorerCommands.CloseCommand.Execute(page, null);
                index = score.Pages.FindIndex(page.AdaptedPart.Uri);
                virtualPath = score.Pages[page.AdaptedPart.Uri];
                score.Pages.Remove(page.AdaptedPart.Uri);
            }
            else if (DeletedPart is Elements.Score)
            {
                var score = (Elements.Score)DeletedPart;
                var project = score.AdaptedPart.Package.ProjectPart.Project;

                index = project.Scores.FindIndex(score.AdaptedPart.Uri);
                virtualPath = project.Scores[score.AdaptedPart.Uri];
                project.Scores.Remove(score.AdaptedPart.Uri);
            }
            else if (DeletedPart is Elements.Resource)
            {
                var resource = (Elements.Resource)DeletedPart;
                var project = resource.AdaptedPart.Package.ProjectPart.Project;

                index = project.Resources.FindIndex(resource.AdaptedPart.Uri);
                virtualPath = project.Resources[resource.AdaptedPart.Uri];
                project.Resources.Remove(resource.AdaptedPart.Uri);

                project.PackagePart.Package.DeletePart(resource.AdaptedPart.Uri);
            }
            else if (DeletedPart is Elements.Composition)
            {
                var composition = (Elements.Composition)DeletedPart;
                var project = composition.AdaptedPart.Package.ProjectPart.Project;

                ProjectExplorerCommands.CloseCommand.Execute(composition, null);
                index = project.Compositions.FindIndex(composition.AdaptedPart.Uri);
                virtualPath = project.Compositions[composition.AdaptedPart.Uri];
                project.Compositions.Remove(composition.AdaptedPart.Uri);
            }

            if (DeletedPart is ViewModelElement)
            {
                var vmElement = (ViewModelElement)DeletedPart;
                var package = vmElement.AdaptedPart.Package;
                if (vmElement.AdaptedElement == null)
                    throw new ApplicationException("Could not load the deleted part's data.");

                package.DeletePart(vmElement.AdaptedPart.Uri);
            }
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            foreach (var command in subCommands)
                command.Execute(null);

            if (DeletedPart is Directory)
            {
                DeletedPart.Parent.Children.Remove(DeletedPart);
                return;
            }
            else if (DeletedPart is Elements.Page)
            {
                var page = (Elements.Page)DeletedPart;
                var score = (Model.Elements.ScoreElement)page.FindOwner<Elements.Score>().AdaptedElement;
                ProjectExplorerCommands.CloseCommand.Execute(page, null);

                score.Pages.Remove(page.AdaptedPart.Uri);
            }
            else if (DeletedPart is Elements.Score)
            {
                var score = (Elements.Score)DeletedPart;
                var project = score.AdaptedPart.Package.ProjectPart.Project;

                project.Scores.Remove(score.AdaptedPart.Uri);
            }
            else if (DeletedPart is Elements.Resource)
            {
                var resource = (Elements.Resource)DeletedPart;
                var project = resource.AdaptedPart.Package.ProjectPart.Project;

                project.Resources.Remove(resource.AdaptedPart.Uri);
            }
            else if (DeletedPart is Elements.Composition)
            {
                var composition = (Elements.Composition)DeletedPart;
                var project = composition.AdaptedPart.Package.ProjectPart.Project;
                ProjectExplorerCommands.CloseCommand.Execute(composition, null);

                project.Compositions.Remove(composition.AdaptedPart.Uri);
            }

            var vmElement = (ViewModelElement)DeletedPart;
            var package = vmElement.AdaptedPart.Package;
            if (vmElement.AdaptedElement == null)
                throw new ApplicationException("Could not load the deleted part's data.");

            package.DeletePart(vmElement.AdaptedPart.Uri);
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            if (DeletedPart is Directory)
                DeletedPart.Parent.Children.Add(DeletedPart);
            else if (DeletedPart is Elements.Resource)
            {
                var resource = (Elements.Resource)DeletedPart;
                var package = resource.AdaptedPart.Package;
                package.RestorePart(resource.AdaptedPart);

                var project = resource.AdaptedPart.Package.ProjectPart.Project;

                project.Resources.Insert(virtualPath, resource.AdaptedPart.Uri, index);
            }
            else
            {
                var vmElement = (ViewModelElement)DeletedPart;
                var package = vmElement.AdaptedPart.Package;

                package.RestorePart(vmElement.AdaptedPart);

                if (DeletedPart is Elements.Page)
                {
                    var page = (Elements.Page)DeletedPart;
                    var score = (Model.Elements.ScoreElement)page.FindOwner<Elements.Score>().AdaptedElement;

                    score.Pages.Insert(virtualPath, page.AdaptedPart.Uri, index);

                    var vmScore = page.FindOwner<Elements.Score>();
                    vmScore.Pages[index] = page;
                    vmScore.Children[index] = page;
                }
                else if (DeletedPart is Elements.Score)
                {
                    var score = (Elements.Score)DeletedPart;
                    var project = score.AdaptedPart.Package.ProjectPart.Project;

                    project.Scores.Insert(virtualPath, score.AdaptedPart.Uri, index);

                    var vmProject = score.FindOwner<Elements.Project>();
                    vmProject.Scores[index] = score;
                    var vmScore = Internal.ProjectExplorerItemHelper.FindItem(virtualPath, vmProject.GetRootDirectory(Elements.RootDirectory.Scores));
                    vmScore.Parent.Children[vmScore.Parent.Children.IndexOf(vmScore)] = score;
                }
                else if (DeletedPart is Elements.Composition)
                {
                    var composition = (Elements.Composition)DeletedPart;
                    var project = composition.AdaptedPart.Package.ProjectPart.Project;

                    project.Compositions.Insert(virtualPath, composition.AdaptedPart.Uri, index);

                    var vmProject = composition.FindOwner<Elements.Project>();
                    vmProject.Compositions[index] = composition;
                    var vmComposition = Internal.ProjectExplorerItemHelper.FindItem(virtualPath, vmProject.GetRootDirectory(Elements.RootDirectory.Compositions));
                    vmComposition.Parent.Children[vmComposition.Parent.Children.IndexOf(vmComposition)] = composition;
                }
            }

            for (int i = subCommands.Count - 1; i >= 0; --i)
                subCommands[i].Execute(null);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the deleted project part.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="IProjectExplorerItem"/>
        /// </para>
        /// <para>
        /// The project part that is to be deleted.<br/>
        /// This property wraps the "DeletedPart" command parameter.
        /// </para>
        /// </value>
        public IProjectExplorerItem DeletedPart
        {
            get { return GetParameterValue<IProjectExplorerItem>("DeletedPart"); }
            set 
            { 
                SetParameterValue<IProjectExplorerItem>("DeletedPart", value); 
                
                if (value != null)
                    Name = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Delete") + " " + value.Name;
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the list of commands that delete subordinate project parts.
        /// </summary>
        private List<DeleteProjectPartCommand> subCommands;
        /// <summary>
        /// Holds the index of the deleted part in the path collection.
        /// </summary>
        private int index;
        /// <summary>
        /// Holds the virtual path to the deleted part.
        /// </summary>
        private string virtualPath;

        #endregion
    }
}
